using FileTransfer.Client.Http.Api;
using FileTransfer.Client.Http.Client;
using FileTransfer.Client.Http.Model;

namespace FileTransfer.Client.Test;

/// <summary>
/// Real backend integration tests for UserControllerApi
/// These tests connect to the actual backend running on http://localhost:8081
/// Make sure the backend is running before executing these tests
/// </summary>
[TestClass]
public class UserControllerApiTest
{
    private ApiClient _apiClient = null!;
    private UserControllerApi? _userApi;
    private static string? _testToken;
    private static UserResponse? _testUser;

    [TestInitialize]
    public void Setup()
    {
        // Create real API client that connects to backend
        _apiClient = ApiHelper.NewApiClient(null);
        _userApi = _apiClient.UserControllerApi;
    }

    [TestCleanup]
    public void Cleanup()
    {
        _apiClient?.Dispose();
    }

    [TestMethod]
    public async Task TestBackendConnectivity()
    {
        try
        {
            var response = await _apiClient.ConnectionControllerApi.TestConnectionAsync();
            Console.WriteLine(response);
            if (response)
            {
                Assert.IsTrue(true, "Backend is running and accessible");
            }
            else
            {
                Assert.Inconclusive(
                    "Backend is not running or not accessible at http://localhost:8081"
                );
            }
        }
        catch (Exception ex)
        {
            Assert.Inconclusive($"Backend connectivity test failed: {ex.Message}");
        }
    }

    [TestMethod]
    public async Task LoginAsync_InvalidCredentials_ShouldReturnUnauthorized()
    {
        try
        {
            // Arrange
            var loginRequest = new UserLoginRequest("invalid_user", "invalid_password");

            // Act & Assert - Expect ApiException for invalid credentials
            await Assert.ThrowsExceptionAsync<ApiException>(async () =>
            {
                await _userApi!.LoginAsync(loginRequest);
            });
        }
        catch (HttpRequestException ex)
        {
            Assert.Inconclusive($"Backend is not accessible: {ex.Message}");
        }
        catch (Exception ex)
            when (ex.Message.Contains("Connection refused")
                || ex.Message.Contains("No connection could be made")
            )
        {
            Assert.Inconclusive("Backend is not running at http://localhost:8081");
        }
    }

    [TestMethod]
    public async Task RegisterAsync_InvalidData_ShouldHandleGracefully()
    {
        try
        {
            // Arrange - Using invalid data to test error handling
            var registerRequest = new UserRegistrationRequest(
                "", // Empty username should fail validation
                "invalid-email", // Invalid email format
                "weak", // Weak password
                ""
            );

            // Act & Assert - Expect validation errors
            var exception = await Assert.ThrowsExceptionAsync<ApiException>(async () =>
            {
                await _userApi!.RegisterAsync(registerRequest);
            });
            // Should receive a 400 Bad Request for validation errors
            Assert.IsTrue(exception.ErrorCode is 400 or 422);
            Console.WriteLine(exception.Message);
        }
        catch (HttpRequestException ex)
        {
            Assert.Inconclusive($"Backend is not accessible: {ex.Message}");
        }
        catch (Exception ex)
            when (ex.Message.Contains("Connection refused")
                || ex.Message.Contains("No connection could be made")
            )
        {
            Assert.Inconclusive("Backend is not running at http://localhost:8081");
        }
    }

    [TestMethod]
    public async Task GetCurrentUserAsync_WithoutAuthentication_ShouldReturnUnauthorized()
    {
        try
        {
            // Act & Assert - Without authentication token, should get 401
            await Assert.ThrowsExceptionAsync<ApiException>(async () =>
            {
                await _userApi!.GetCurrentUserAsync();
            });
        }
        catch (HttpRequestException ex)
        {
            Assert.Inconclusive($"Backend is not accessible: {ex.Message}");
        }
        catch (Exception ex)
            when (ex.Message.Contains("Connection refused")
                || ex.Message.Contains("No connection could be made")
            )
        {
            Assert.Inconclusive("Backend is not running at http://localhost:8081");
        }
    }

    [TestMethod]
    public async Task ChangePasswordAsync_WithoutAuthentication_ShouldReturnUnauthorized()
    {
        try
        {
            // Arrange
            var changePasswordRequest = new ChangePasswordRequest("oldpassword", "newpassword");

            // Act & Assert - Without authentication, should get 401
            await Assert.ThrowsExceptionAsync<ApiException>(async () =>
            {
                await _userApi!.ChangePasswordAsync(changePasswordRequest);
            });
        }
        catch (HttpRequestException ex)
        {
            Assert.Inconclusive($"Backend is not accessible: {ex.Message}");
        }
        catch (Exception ex)
            when (ex.Message.Contains("Connection refused")
                || ex.Message.Contains("No connection could be made")
            )
        {
            Assert.Inconclusive("Backend is not running at http://localhost:8081");
        }
    }

    /// <summary>
    /// Test successful user registration and login flow
    /// </summary>
    [TestMethod]
    public async Task AuthenticationFlow_CompleteFlow_ShouldSucceed()
    {
        try
        {
            var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
            var username = $"testuser_{timestamp}";
            var email = $"test_{timestamp}@example.com";
            var password = "TestPassword123!";

            // Step 1: Register a test user
            var registerRequest = new UserRegistrationRequest(
                username,
                email,
                password,
                "Test User"
            );

            var registerResponse = await _userApi!.RegisterAsync(registerRequest);
            Assert.IsNotNull(registerResponse?.Data, "Registration should return auth response");
            Assert.IsNotNull(registerResponse.Data.Token, "Registration should include JWT token");
            Assert.IsNotNull(registerResponse.Data.User, "Registration should include user data");

            Console.WriteLine(
                $"Registration successful for user: {registerResponse.Data.User.Username}"
            );
            Console.WriteLine($"JWT Token received: {registerResponse.Data.Token[..20]}...");

            // Step 2: Login with the same credentials
            var loginRequest = new UserLoginRequest(username, password);
            var loginResponse = await _userApi.LoginAsync(loginRequest);

            Assert.IsNotNull(loginResponse?.Data, "Login should return auth response");
            Assert.IsNotNull(loginResponse.Data.Token, "Login should include JWT token");
            Assert.IsNotNull(loginResponse.Data.User, "Login should include user data");
            Assert.AreEqual(username, loginResponse.Data.User.Username, "Username should match");

            // Store the token for subsequent tests
            _testToken = loginResponse.Data.Token;
            _testUser = loginResponse.Data.User;

            Console.WriteLine($"Login successful for user: {loginResponse.Data.User.Username}");
            Console.WriteLine($"JWT Token received: {loginResponse.Data.Token[..20]}...");
        }
        catch (HttpRequestException ex)
        {
            Assert.Inconclusive($"Backend is not accessible: {ex.Message}");
        }
        catch (Exception ex)
            when (ex.Message.Contains("Connection refused")
                || ex.Message.Contains("No connection could be made")
            )
        {
            Assert.Inconclusive("Backend is not running at http://localhost:8081");
        }
    }

    /// <summary>
    /// Test successful login with valid credentials and JWT token acquisition
    /// </summary>
    [TestMethod]
    public async Task LoginAsync_ValidCredentials_ShouldReturnJwtToken()
    {
        try
        {
            // First, create a test user or use existing valid credentials
            // For this test, we'll try with a known user or create one
            var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
            var username = $"logintest_{timestamp}";
            var email = $"logintest_{timestamp}@example.com";
            var password = "TestPassword123!";

            // Create test user first
            var registerRequest = new UserRegistrationRequest(
                username,
                email,
                password,
                "Login Test User"
            );
            await _userApi!.RegisterAsync(registerRequest);

            // Now test login
            var loginRequest = new UserLoginRequest(username, password);
            var response = await _userApi.LoginAsync(loginRequest);

            // Assert authentication response
            Assert.IsNotNull(response, "Login response should not be null");
            Assert.IsNotNull(response.Data, "Login response data should not be null");

            var authResponse = response.Data;
            Assert.IsNotNull(authResponse.Token, "JWT token should not be null");
            Assert.IsFalse(
                string.IsNullOrWhiteSpace(authResponse.Token),
                "JWT token should not be empty"
            );
            Assert.AreEqual("Bearer", authResponse.Type, "Token type should be Bearer");

            // Validate user information
            Assert.IsNotNull(authResponse.User, "User information should be included");
            Assert.AreEqual(username, authResponse.User.Username, "Username should match");
            Assert.AreEqual(email, authResponse.User.Email, "Email should match");

            // Store token for subsequent tests
            _testToken = authResponse.Token;
            _testUser = authResponse.User;

            Console.WriteLine($"Login successful - Token: {authResponse.Token[..20]}...");
            Console.WriteLine($"User: {authResponse.User.Username} ({authResponse.User.Email})");
        }
        catch (HttpRequestException ex)
        {
            Assert.Inconclusive($"Backend is not accessible: {ex.Message}");
        }
        catch (Exception ex)
            when (ex.Message.Contains("Connection refused")
                || ex.Message.Contains("No connection could be made")
            )
        {
            Assert.Inconclusive("Backend is not running at http://localhost:8081");
        }
    }

    /// <summary>
    /// Test authenticated user information retrieval
    /// </summary>
    [TestMethod]
    public async Task GetCurrentUserAsync_WithValidAuthentication_ShouldReturnUserInfo()
    {
        try
        {
            // First ensure we have a valid token
            await EnsureValidTokenExists();

            if (string.IsNullOrEmpty(_testToken))
            {
                Assert.Inconclusive("No valid token available for authenticated test");
                return;
            }

            // Create authenticated API client
            var authenticatedClient = CreateAuthenticatedApiClient(_testToken);
            var authenticatedUserApi = authenticatedClient.UserControllerApi;

            // Test authenticated request
            var response = await authenticatedUserApi.GetCurrentUserAsync();

            Assert.IsNotNull(response, "User response should not be null");
            Assert.IsNotNull(response.Data, "User data should not be null");

            var userData = response.Data;
            Assert.IsFalse(
                string.IsNullOrWhiteSpace(userData.Username),
                "Username should not be empty"
            );
            Assert.IsFalse(string.IsNullOrWhiteSpace(userData.Email), "Email should not be empty");

            // If we have stored test user, verify it matches
            if (_testUser != null)
            {
                Assert.AreEqual(
                    _testUser.Username,
                    userData.Username,
                    "Username should match authenticated user"
                );
                Assert.AreEqual(
                    _testUser.Email,
                    userData.Email,
                    "Email should match authenticated user"
                );
            }

            Console.WriteLine(
                $"Successfully retrieved user info: {userData.Username} ({userData.Email})"
            );

            authenticatedClient.Dispose();
        }
        catch (HttpRequestException ex)
        {
            Assert.Inconclusive($"Backend is not accessible: {ex.Message}");
        }
        catch (Exception ex)
            when (ex.Message.Contains("Connection refused")
                || ex.Message.Contains("No connection could be made")
            )
        {
            Assert.Inconclusive("Backend is not running at http://localhost:8081");
        }
    }

    /// <summary>
    /// Test JWT token validation by making authenticated requests
    /// </summary>
    [TestMethod]
    public async Task JwtToken_ValidToken_ShouldAllowAuthenticatedRequests()
    {
        try
        {
            // Ensure we have a valid token
            await EnsureValidTokenExists();

            if (string.IsNullOrEmpty(_testToken))
            {
                Assert.Inconclusive("No valid token available for JWT validation test");
                return;
            }

            // Test 1: Valid token should allow access to protected endpoints
            var authenticatedClient = CreateAuthenticatedApiClient(_testToken);
            var authenticatedUserApi = authenticatedClient.UserControllerApi;

            var userResponse = await authenticatedUserApi.GetCurrentUserAsync();
            Assert.IsNotNull(userResponse?.Data, "Valid token should allow access to user data");

            // Test 2: Token should have proper format (JWT has 3 parts separated by dots)
            var tokenParts = _testToken.Split('.');
            Assert.AreEqual(
                3,
                tokenParts.Length,
                "JWT token should have 3 parts (header.payload.signature)"
            );
            Assert.IsTrue(
                tokenParts.All(part => !string.IsNullOrWhiteSpace(part)),
                "All JWT parts should be non-empty"
            );

            // Test 3: Invalid token should fail
            var invalidClient = CreateAuthenticatedApiClient("invalid.jwt.token");
            var invalidUserApi = invalidClient.UserControllerApi;

            await Assert.ThrowsExceptionAsync<ApiException>(
                async () =>
                {
                    await invalidUserApi.GetCurrentUserAsync();
                },
                "Invalid token should result in ApiException"
            );

            Console.WriteLine(
                $"JWT token validation successful - Token format: {tokenParts[0].Length}.{tokenParts[1].Length}.{tokenParts[2].Length}"
            );

            authenticatedClient.Dispose();
            invalidClient.Dispose();
        }
        catch (HttpRequestException ex)
        {
            Assert.Inconclusive($"Backend is not accessible: {ex.Message}");
        }
        catch (Exception ex)
            when (ex.Message.Contains("Connection refused")
                || ex.Message.Contains("No connection could be made")
            )
        {
            Assert.Inconclusive("Backend is not running at http://localhost:8081");
        }
    }

    /// <summary>
    /// Test token expiration handling
    /// </summary>
    [TestMethod]
    public async Task Authentication_ExpiredToken_ShouldRequireReauth()
    {
        try
        {
            // Create an obviously expired/invalid token
            var expiredToken =
                "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";

            var expiredClient = CreateAuthenticatedApiClient(expiredToken);
            var expiredUserApi = expiredClient.UserControllerApi;

            // Should throw exception for expired/invalid token
            await Assert.ThrowsExceptionAsync<ApiException>(
                async () =>
                {
                    await expiredUserApi.GetCurrentUserAsync();
                },
                "Expired token should result in authentication failure"
            );

            Console.WriteLine("Expired token correctly rejected by server");

            expiredClient.Dispose();
        }
        catch (HttpRequestException ex)
        {
            Assert.Inconclusive($"Backend is not accessible: {ex.Message}");
        }
        catch (Exception ex)
            when (ex.Message.Contains("Connection refused")
                || ex.Message.Contains("No connection could be made")
            )
        {
            Assert.Inconclusive("Backend is not running at http://localhost:8081");
        }
    }

    /// <summary>
    /// Test change password with valid authentication
    /// </summary>
    [TestMethod]
    public async Task ChangePasswordAsync_WithValidAuthentication_ShouldSucceed()
    {
        try
        {
            // Ensure we have a valid token and create a fresh user for this test
            var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
            var username = $"pwdtest_{timestamp}";
            var email = $"pwdtest_{timestamp}@example.com";
            var oldPassword = "OldPassword123!";
            var newPassword = "NewPassword456!";

            // Create test user
            var registerRequest = new UserRegistrationRequest(
                username,
                email,
                oldPassword,
                "Password Test User"
            );
            var registerResponse = await _userApi!.RegisterAsync(registerRequest);
            var token = registerResponse?.Data?.Token;

            Assert.IsNotNull(token, "Should have valid token from registration");

            // Test password change with authentication
            var authenticatedClient = CreateAuthenticatedApiClient(token);
            var authenticatedUserApi = authenticatedClient.UserControllerApi;

            var changePasswordRequest = new ChangePasswordRequest(oldPassword, newPassword);
            var changeResponse = await authenticatedUserApi.ChangePasswordAsync(
                changePasswordRequest
            );

            Assert.IsNotNull(changeResponse, "Change password response should not be null");

            // Test login with new password
            var loginRequest = new UserLoginRequest(username, newPassword);
            var loginResponse = await _userApi.LoginAsync(loginRequest);

            Assert.IsNotNull(loginResponse?.Data, "Should be able to login with new password");
            Assert.IsNotNull(loginResponse.Data.Token, "New login should provide JWT token");

            // Test that old password no longer works
            var oldPasswordRequest = new UserLoginRequest(username, oldPassword);
            await Assert.ThrowsExceptionAsync<ApiException>(
                async () =>
                {
                    await _userApi.LoginAsync(oldPasswordRequest);
                },
                "Old password should no longer work"
            );

            Console.WriteLine(
                "Password change successful - old password invalidated, new password works"
            );

            authenticatedClient.Dispose();
        }
        catch (HttpRequestException ex)
        {
            Assert.Inconclusive($"Backend is not accessible: {ex.Message}");
        }
        catch (Exception ex)
            when (ex.Message.Contains("Connection refused")
                || ex.Message.Contains("No connection could be made")
            )
        {
            Assert.Inconclusive("Backend is not running at http://localhost:8081");
        }
    }

    #region Helper Methods

    /// <summary>
    /// Ensures a valid token exists for testing, creating one if necessary
    /// </summary>
    private async Task EnsureValidTokenExists()
    {
        if (!string.IsNullOrEmpty(_testToken))
        {
            return; // Already have a token
        }

        // Create a test user and get token
        var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
        var username = $"authtest_{timestamp}";
        var email = $"authtest_{timestamp}@example.com";
        var password = "TestPassword123!";

        var registerRequest = new UserRegistrationRequest(
            username,
            email,
            password,
            "Auth Test User"
        );
        var response = await _userApi!.RegisterAsync(registerRequest);

        _testToken = response?.Data?.Token;
        _testUser = response?.Data?.User;
    }

    /// <summary>
    /// Creates an API client with Bearer token authentication
    /// </summary>
    private ApiClient CreateAuthenticatedApiClient(string bearerToken)
    {
        var config = new Configuration();
        config.DefaultHeaders["Authorization"] = $"Bearer {bearerToken}";
        return ApiHelper.NewApiClient(config);
    }

    #endregion
}
